// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/pepper/pepper_try_catch.h"

#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
#include "content/renderer/pepper/v8_var_converter.h"
#include "gin/converter.h"
#include "ppapi/shared_impl/ppapi_globals.h"
#include "ppapi/shared_impl/var_tracker.h"

namespace content {

namespace {

    const char kConversionException[] = "Error: Failed conversion between PP_Var and V8 value";
    const char kInvalidException[] = "Error: An invalid exception was thrown.";

} // namespace

PepperTryCatch::PepperTryCatch(PepperPluginInstanceImpl* instance,
    V8VarConverter* var_converter)
    : instance_(instance)
    , var_converter_(var_converter)
{
}

PepperTryCatch::~PepperTryCatch() { }

v8::Local<v8::Value> PepperTryCatch::ToV8(PP_Var var)
{
    if (HasException()) {
        SetException(kConversionException);
        return v8::Local<v8::Value>();
    }

    v8::Local<v8::Value> result;
    bool success = var_converter_->ToV8Value(var, GetContext(), &result);
    if (!success) {
        SetException(kConversionException);
        return v8::Local<v8::Value>();
    }
    return result;
}

ppapi::ScopedPPVar PepperTryCatch::FromV8(v8::Local<v8::Value> v8_value)
{
    if (HasException() || v8_value.IsEmpty()) {
        SetException(kConversionException);
        return ppapi::ScopedPPVar();
    }
    ppapi::ScopedPPVar result;
    bool success = var_converter_->FromV8ValueSync(v8_value, GetContext(), &result);
    if (!success) {
        SetException(kConversionException);
        return ppapi::ScopedPPVar();
    }
    return result;
}

PepperTryCatchV8::PepperTryCatchV8(PepperPluginInstanceImpl* instance,
    V8VarConverter* var_converter,
    v8::Isolate* isolate)
    : PepperTryCatch(instance, var_converter)
    , exception_(PP_MakeUndefined())
{
    // Typically when using PepperTryCatchV8 we are passed an isolate. We verify
    // that this isolate is the same as the plugin isolate.
    DCHECK(isolate == instance_->GetIsolate());

    // We assume that a handle scope and context has been setup by the user of
    // this class. This is typically true because this class is used when calling
    // into the plugin from JavaScript. We want to use whatever v8 context the
    // caller is in.
}

PepperTryCatchV8::~PepperTryCatchV8()
{
    ppapi::PpapiGlobals::Get()->GetVarTracker()->ReleaseVar(exception_);
}

bool PepperTryCatchV8::HasException()
{
    return GetContext().IsEmpty() || exception_.type != PP_VARTYPE_UNDEFINED;
}

v8::Local<v8::Context> PepperTryCatchV8::GetContext()
{
    // When calling from JS into the plugin always use the current context.
    return instance_->GetIsolate()->GetCurrentContext();
}

bool PepperTryCatchV8::ThrowException()
{
    if (!HasException())
        return false;

    // If there is no context then we have an exception but we don't try to throw
    // it into v8.
    if (GetContext().IsEmpty())
        return true;

    std::string message(kInvalidException);
    ppapi::StringVar* message_var = ppapi::StringVar::FromPPVar(exception_);
    if (message_var)
        message = message_var->value();
    instance_->GetIsolate()->ThrowException(v8::Exception::Error(
        gin::StringToV8(instance_->GetIsolate(), message)));

    ppapi::PpapiGlobals::Get()->GetVarTracker()->ReleaseVar(exception_);
    exception_ = PP_MakeUndefined();
    return true;
}

void PepperTryCatchV8::ThrowException(const char* message)
{
    SetException(message);
    ThrowException();
}

void PepperTryCatchV8::SetException(const char* message)
{
    if (HasException())
        return;

    exception_ = ppapi::StringVar::StringToPPVar(message);
}

PepperTryCatchVar::PepperTryCatchVar(PepperPluginInstanceImpl* instance,
    V8VarConverter* var_converter,
    PP_Var* exception)
    : PepperTryCatch(instance, var_converter)
    , handle_scope_(instance_->GetIsolate())
    , try_catch_(instance_->GetIsolate())
    , exception_(exception)
    , exception_is_set_(false)
{
    // Store a handle to the context here for 2 reasons:
    // 1) To hold a handle to it in case all other handles are destroyed.
    // 2) Because calling PepperPluginInstanceImpl::GetMainWorldContext() later
    //    can result in trying to access the plugin element. However the plugin
    //    element may have been destroyed during the PepperTryCatchVar (for
    //    example if a script is executed which destroys the plugin element). So
    //    we want to avoid accessing the plugin element again beyond this point.
    context_ = instance_->GetMainWorldContext();

    // We switch to the plugin context if it's not empty.
    if (!context_.IsEmpty())
        context_->Enter();
}

PepperTryCatchVar::~PepperTryCatchVar()
{
    if (!context_.IsEmpty())
        context_->Exit();
}

bool PepperTryCatchVar::HasException()
{
    if (exception_is_set_)
        return true;

    std::string exception_message;
    if (context_.IsEmpty()) {
        exception_message = "The v8 context has been destroyed.";
    } else if (try_catch_.HasCaught()) {
        v8::Local<v8::Message> message(try_catch_.Message());
        if (!message.IsEmpty()) {
            v8::String::Utf8Value utf8(try_catch_.Message()->Get());
            exception_message = std::string(*utf8, utf8.length());
        } else {
            exception_message = "There was a v8 exception.";
        }
    }

    if (!exception_message.empty()) {
        exception_is_set_ = true;
        if (exception_)
            *exception_ = ppapi::StringVar::StringToPPVar(exception_message);
    }

    return exception_is_set_;
}

v8::Local<v8::Context> PepperTryCatchVar::GetContext()
{
    return context_;
}

void PepperTryCatchVar::SetException(const char* message)
{
    if (exception_is_set_)
        return;

    if (exception_)
        *exception_ = ppapi::StringVar::StringToPPVar(message, strlen(message));
    exception_is_set_ = true;
}

} // namespace content
